#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <cstring>
#include "../logic_layer/hash/archivoDirecto.h"
#include "../logic_layer/bplustree/bplusfilehandler/BPlusTreeFile.h"
#include "../logic_layer/bplustree/BPlusTreeTemplate.h"

#define _ATTR_DELIM '|'
#define HASHFILE_NAME_ARG_POS 1

string registro;
string loadFileName;
string stopWordsFile;
int deleteId = 0;
bool deleteReg = false;
bool truncateFile = false;

class RankingTerm {

	friend ostream& operator<<(ostream& out, RankingTerm& term);
	friend bool compareRankingTerm (RankingTerm first, RankingTerm second);

	std::string term;
	int count;

public:

	RankingTerm(std::string term) {
		this->term = term;
		this->count = 1;
	}

	void increaseCount() {
		count++;
	}

	std::string getTerm() {
		return term;
	}

};

bool compareRankingTerm (RankingTerm first, RankingTerm second)
{

	return (first.count > second.count)? true : false;

}

ostream& operator<<(ostream& out, RankingTerm& term) {
	out << "Term: " << term.term << " Count: " << term.count << endl;
	return out;

}

//Statistics
int termCount = 0;
int phraseCount = 0;
std::list<std::string> termsNotFound;
std::list<RankingTerm> termsFound;

typedef struct {
	size_t id;
	std::string phrase;
	std::string surname;
	std::string name;

	bool parseData(std::string &line) {
		std::stringstream ss(line);

		char c;
		if (!(ss >> id && ss >> c && c == _ATTR_DELIM))
			return false;

		if (!getline(ss, phrase, _ATTR_DELIM))
			return false;

		if (!getline(ss, surname, _ATTR_DELIM))
			return false;

		if (!getline(ss, name, _ATTR_DELIM))
			name.clear();

		return true;
	}

	bool parseDataNoID(std::string &line) {
		std::stringstream ss(line);

		if (!getline(ss, name, _ATTR_DELIM))
			return false;

		if (!getline(ss, surname, _ATTR_DELIM))
			return false;

		if (!getline(ss, phrase, _ATTR_DELIM))
			name.clear();

		return true;
	}

} HashRegisterBuffer_t;

/**
 *  Esta clase es un diccionario de stop-words
 *  Se utiliza para filtrar stop-words
 */
class StopWordsDictionary {
	std::list<std::string> stopWords;

public:
	void load(std::ifstream &ifs) {
		std::string lineBuffer;

		while (getline(ifs, lineBuffer)) {
			stopWords.push_back(lineBuffer);
		}
	}

	bool isStopWord(std::string &str) {
		for (std::list<std::string>::iterator it = stopWords.begin();
				it != stopWords.end(); it++) {
			if (it->compare(str) == 0)
				return true;
		}

		return false;
	}

};

/**
 * Estructura invalid symbol
 * Representa un caracter inválido (tildes y demás)
 *  y tiene un posible reemplazo para volverlo valido
 */
struct invalidSymbol {
	std::string m_symbol;
	std::string m_replacement;

	invalidSymbol(std::string const &symbol, std::string const &replacement) :
			m_symbol(symbol), m_replacement(replacement) {
	}
};

class InvalidSymbolsDictionary {
private:
	typedef std::list<invalidSymbol> invalidSymbolsCollection;

public:
	typedef invalidSymbolsCollection::iterator iterator;

public:
	InvalidSymbolsDictionary() {
		initialize();
	}
	;

	iterator begin() {
		return invalidSymbols.begin();
	}

	iterator end() {
		return invalidSymbols.end();
	}

private:
	invalidSymbolsCollection invalidSymbols;

	void initialize() {
		invalidSymbols.push_back(invalidSymbol("á", "a"));
		invalidSymbols.push_back(invalidSymbol("à", "a"));
		invalidSymbols.push_back(invalidSymbol("â", "a"));
		invalidSymbols.push_back(invalidSymbol("ä", "a"));

		invalidSymbols.push_back(invalidSymbol("é", "e"));
		invalidSymbols.push_back(invalidSymbol("è", "e"));
		invalidSymbols.push_back(invalidSymbol("ê", "e"));
		invalidSymbols.push_back(invalidSymbol("ë", "e"));

		invalidSymbols.push_back(invalidSymbol("í", "i"));
		invalidSymbols.push_back(invalidSymbol("ì", "i"));
		invalidSymbols.push_back(invalidSymbol("î", "i"));
		invalidSymbols.push_back(invalidSymbol("ï", "i"));

		invalidSymbols.push_back(invalidSymbol("ó", "o"));
		invalidSymbols.push_back(invalidSymbol("ò", "o"));
		invalidSymbols.push_back(invalidSymbol("ô", "o"));
		invalidSymbols.push_back(invalidSymbol("ö", "o"));

		invalidSymbols.push_back(invalidSymbol("ú", "u"));
		invalidSymbols.push_back(invalidSymbol("ù", "u"));
		invalidSymbols.push_back(invalidSymbol("û", "u"));
		invalidSymbols.push_back(invalidSymbol("ü", "u"));

		invalidSymbols.push_back(invalidSymbol(",", " "));
		invalidSymbols.push_back(invalidSymbol(".", " "));
		invalidSymbols.push_back(invalidSymbol(";", " "));
		invalidSymbols.push_back(invalidSymbol(":", " "));
		invalidSymbols.push_back(invalidSymbol("?", " "));
		invalidSymbols.push_back(invalidSymbol("|", " "));
		invalidSymbols.push_back(invalidSymbol("¿", " "));
		invalidSymbols.push_back(invalidSymbol("!", " "));
		invalidSymbols.push_back(invalidSymbol("¡", " "));
		invalidSymbols.push_back(invalidSymbol("[", " "));
		invalidSymbols.push_back(invalidSymbol("]", " "));
		invalidSymbols.push_back(invalidSymbol("(", " "));
		invalidSymbols.push_back(invalidSymbol(")", " "));
		invalidSymbols.push_back(invalidSymbol("<", " "));
		invalidSymbols.push_back(invalidSymbol(">", " "));
		invalidSymbols.push_back(invalidSymbol("'", " "));
		invalidSymbols.push_back(invalidSymbol("/", " "));
		invalidSymbols.push_back(invalidSymbol("-", " "));
		invalidSymbols.push_back(invalidSymbol("~", " "));
		invalidSymbols.push_back(invalidSymbol("$", " "));
		invalidSymbols.push_back(invalidSymbol("%", " "));
		invalidSymbols.push_back(invalidSymbol("#", " "));
		invalidSymbols.push_back(invalidSymbol("@", " "));
		invalidSymbols.push_back(invalidSymbol("º", " "));
		invalidSymbols.push_back(invalidSymbol("ª", " "));
		invalidSymbols.push_back(invalidSymbol("*", " "));
		invalidSymbols.push_back(invalidSymbol("+", " "));
		invalidSymbols.push_back(invalidSymbol("^", " "));
		invalidSymbols.push_back(invalidSymbol("¬", " "));
		invalidSymbols.push_back(invalidSymbol("½", " "));
		invalidSymbols.push_back(invalidSymbol("¨", " "));
		invalidSymbols.push_back(invalidSymbol("…", " "));
		invalidSymbols.push_back(invalidSymbol("\"", " "));
		invalidSymbols.push_back(invalidSymbol("\\", " "));
	}

};

InvalidSymbolsDictionary invalidSymbolsDictionary;

/**
 * Función que reemplaza cada caracter considerado "simbolo extraño"
 *  por un "equivalente aceptable"
 *
 * @param str
 */
void replaceSymbols(std::string &str) {
	InvalidSymbolsDictionary::iterator it = invalidSymbolsDictionary.begin();

	for (; it != invalidSymbolsDictionary.end(); it++) {
		size_t pos = str.find(it->m_symbol);
		while (pos != std::string::npos) {
			str.replace(pos, it->m_symbol.size(), it->m_replacement);
			pos = str.find(it->m_symbol);
		}
	}
}

/**
 *  Función personalizada que pasa un string a minúsculas
 *  Nota: es personalizada porque tolower parece que rompia las ñ
 *
 * @param str
 */
void toLower(std::string &str) {
	std::string upper_enie("Ñ");
	std::string lower_enie("ñ");

	// reemplazo las Ñ por ñ
	size_t pos = str.find(upper_enie);
	while (pos != std::string::npos) {
		str.replace(pos, upper_enie.size(), lower_enie);
		pos = str.find(upper_enie);
	}

	// ahora aplico tolower a las demás letras
	for (std::string::iterator it = str.begin(); it != str.end(); it++) {
		if (*it <= 'Z' && *it >= 'A')
			*it -= 'Z' - 'z';
	}
}

/**
 * Función encargada de realizar la normalización de palabras
 *
 * @param word
 * @return
 */
std::string normalize(std::string str) {
	std::string normalizedStr(str);
	std::stringstream ss;

	// paso las letras de la palabra a minúsculas
	toLower(normalizedStr);

	// realizo reemplazos de tildes y otros símbolos
	replaceSymbols(normalizedStr);

	return normalizedStr;
}

void parseArgs(int argc, char* argv[]) {

	for (int i = 2; i < argc; ++i) {
		if (strcmp(argv[i], "-i") == 0) {
			registro.assign(argv[i + 1]);
			i++;
			continue;
		}

		if (strcmp(argv[i], "-d") == 0) {
			deleteReg = true;
			deleteId = atoi(argv[i + 1]);
			i++;
			continue;
		}

		if (strcmp(argv[i], "-l") == 0) {
			loadFileName.assign(argv[i + 1]);
			stopWordsFile.assign(argv[i + 2]);
			i += 2;
			continue;
		}

		if (strcmp(argv[i], "-t") == 0) {
			truncateFile = true;
			continue;
		}
	}
}

void invalid_args() {
	std::cerr << "Argumentos inválidos. " << std::endl
			<< "Uso: app hashFile treeFile [-l <fileName> <stopWords> | -t]"
			<< std::endl;
	exit(1);
}

void parsePhrase(
		BPlusTreeTemplate<DTRkey_t, DTRvalue_t, keyComparator_func> &tree,
		StopWordsDictionary &stopWordsDictionary, std::string &phrase) {

	cout << phrase << endl;

	std::string normalizedPhrase = normalize(phrase);
	std::stringstream ss(normalizedPhrase);
	std::string word;
	std::string normalizedWord;

	while (ss >> word) {

		// verificar que no sea stop-word
		if (stopWordsDictionary.isStopWord(word))
			continue;

//		DTRkey_t term("axcv");
//		searchResult_t resultado = tree.SearchRegister(term);
//		cout << "term: " << normalizedWord << endl;
//		cout << "resultado: " << resultado.valid << endl;
//		cout << "resultadoKey: " << resultado.result->getKey() << endl;
//		cout<<">2<"<<endl;
//		resultado = tree.SearchNextRegister(resultado);
//				cout << "term: " << normalizedWord << endl;
//				cout << "resultado: " << resultado.valid << endl;
//				cout << "resultadoKey: " << resultado.result->getKey() << endl;
//		exit(0);

		termCount++;

		DTRkey_t term(word);

		searchResult_t resultado = tree.SearchRegister(term);

		if (word.compare(resultado.result->getKey()) == 0) {

			bool found = false;

			for (std::list<RankingTerm>::iterator it = termsFound.begin();

			it != termsFound.end(); it++) {

				if (it->getTerm().compare(word) == 0) {

					it->increaseCount();

					found = true;

					break;
				}
			}

			if (!found) {
				termsFound.push_back(RankingTerm(word));
			}

		} else {

			termsNotFound.push_back(word);

		}

	}

}

void loadFile(std::string &inputFile, ArchivoDirecto &hash,
		BPlusTreeTemplate<DTRkey_t, DTRvalue_t, keyComparator_func> &tree,
		StopWordsDictionary &stopWords) {

	cerr << "loading file" << endl;

	std::ifstream inFile;
	std::string lineBuffer;
	size_t lineNumber = 0;

	inFile.open(inputFile.c_str());

	while (getline(inFile, lineBuffer)) {

		HashRegisterBuffer_t registerData;

		if (lineBuffer.empty()) {
			continue;
		}

		if (!registerData.parseDataNoID(lineBuffer)) {

			std::cerr << "Error al parsear el archivo en la línea: "
					<< lineNumber << std::endl;
			exit(12);

		} else {

			hash.addRecord(lineNumber + 1, registerData.phrase,
					registerData.surname, registerData.name);

			parsePhrase(tree, stopWords, registerData.phrase);

		}

		lineNumber++;
	}

	phraseCount += lineNumber;
}

void loadStopWords(StopWordsDictionary &stopWordsDictionary) {

	ifstream input(stopWordsFile);

	stopWordsDictionary.load(input);

}

void printStatistics(ostream &out) {

	out << "Cantidad terminos promedio por frase: " << termCount/phraseCount << endl;

	out << "Tasa de fallo de terminos: " << termsNotFound.size()/termCount << endl;

	out << "Lista de terminos no encontrados: " << endl;

	for (std::list<std::string>::iterator it = termsNotFound.begin();

		it != termsNotFound.end(); it++) {

			out << *it << " ";

		}

	out << endl;

	termsFound.sort(compareRankingTerm);

	out << "10 Terminos mas populares: " << endl;

	int i = 0;

	for (std::list<RankingTerm>::iterator it = termsFound.begin();

	it != termsFound.end(); it++) {

		i++;

		out << *it << endl;

		if (i == 10) break;

	}

}

int main(int argc, char **argv) {

	if (argc < 4)
		invalid_args();

	parseArgs(argc, argv);

	//Data Structures
	size_t blkSize = 1024 * 4;

	ArchivoDirecto hash(std::string(argv[HASHFILE_NAME_ARG_POS]), blkSize,
			truncateFile);

	BPlusTreeFile treeFile(argv[2], blkSize, false);
	BPlusTreeTemplate<DTRkey_t, DTRvalue_t, keyComparator_func> termsTree(
			&treeFile);

	StopWordsDictionary stopWordsDictionary;

	if (!stopWordsFile.empty())
		loadStopWords(stopWordsDictionary);

	if (!loadFileName.empty())
		loadFile(loadFileName, hash, termsTree, stopWordsDictionary);

//	if (!registro.empty()) {
//
//		cerr << "adding register" << endl;
//		HashRegisterBuffer_t registerData;
//
//		if (!registerData.parseData(registro)) {
//			std::cerr << "Error al parsear el registro a insertar.";
//			exit(12);
//		}
//
//		hash.addRecord(registerData.id, registerData.name, registerData.surname,
//				registerData.phrase);
//	}
//}
//
//if (deleteReg == true) {
//	cerr<<"delete register"<<endl;
//	hash.removeRecord(deleteId);
//}

	printStatistics(cout);

	return 0;
}

